using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

using Expression = System.Linq.Expressions.Expression;

namespace EMan.GUI.Common.Lambda
{
  public static class Generic
  {
    public static TService GetService< TService >( this IServiceProvider isp )
      where TService : class
    {
      return isp.GetService( typeof( TService ) )
             as TService;
    }
  }

  public class LambdaExtension : MarkupExtension
  {
    public string Code { get; private set; }

    public LambdaExtension( string _code )
    {
      Code = _code;
    }

    public override object ProvideValue( IServiceProvider isp )
    {
      var tp = isp.GetService< IProvideValueTarget >();
      if( tp.TargetObject is Binding ) return new SingleConverter( Code );
      if( tp.TargetObject is MultiBinding ) return new MultiConverter( Code );
      return null;
    }
  }

  internal class SingleConverter : IValueConverter
  {
    public Delegate Operation { get; private set; }
    public string Code { get; private set; }

    public SingleConverter( string _code )
    {
      Code = _code;
    }

    #region IValueConverter Members
    public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
    {
      // Check for design mode. 
      if( ( bool )( DesignerProperties.IsInDesignModeProperty.GetMetadata( typeof( DependencyObject ) ).DefaultValue ) )
        return false;

      if( Operation == null )
      {
        ConstructOperation( value, targetType );
      }
      return Operation.DynamicInvoke( value );
    }

    private void ConstructOperation( object value, Type targetType )
    {
      var opi = Code.IndexOf( "=>" );
      if( opi < 0 ) throw new Exception( "No lambda operator =>" );
      var param = Code.Substring( 0, opi ).Trim();
      var body = Code.Substring( opi + 2 ).Trim();
      var p = Expression.Parameter( value.GetType(), param );
      var lambda = DynamicExpression.ParseLambda( new ParameterExpression[] {p}, targetType, body, value );
      Operation = lambda.Compile();
    }

    public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
    {
      throw new NotImplementedException();
    }
    #endregion
  }

  internal class MultiConverter : IMultiValueConverter
  {
    public Delegate Operation { get; private set; }
    public string Code { get; private set; }

    public MultiConverter( string _code )
    {
      Code = _code;
    }

    #region IMultiValueConverter Members
    public object Convert( object[] values, Type targetType, object parameter, CultureInfo culture )
    {
      if( Operation == null )
      {
        var paramSideLength = Code.IndexOf( "=>" );
        if( paramSideLength < 0 ) throw new Exception( "No lambda operator =>" );
        var paramSide = Code.Substring( 0, paramSideLength );
        var paramNames = paramSide.Split( ',' );
        var bodySide = Code.Substring( paramSideLength + 2 );
        var pes = new ParameterExpression[paramNames.Length];
        for( var i = 0; i < values.Length; i++ )
        {
          pes[ i ] = Expression.Parameter( values[ i ].GetType(), paramNames[ i ] );
        }
        var lambda = DynamicExpression.ParseLambda( pes, targetType, bodySide, values );
        Operation = lambda.Compile();
      }
      return Operation.DynamicInvoke( values );
    }

    public object[] ConvertBack( object value, Type[] targetTypes, object parameter, CultureInfo culture )
    {
      throw new NotImplementedException();
    }
    #endregion
  }
}